﻿using System;

using GTM = Gadgeteer.Modules;
using GTI = Gadgeteer.SocketInterfaces;

namespace Gadgeteer.Modules.GHIElectronics
{
    // -- CHANGE FOR MICRO FRAMEWORK 4.2 --
    // If you want to use Serial, SPI, or DaisyLink (which includes GTI.SoftwareI2CBus), you must do a few more steps
    // since these have been moved to separate assemblies for NETMF 4.2 (to reduce the minimum memory footprint of Gadgeteer)
    // 1) add a reference to the assembly (named Gadgeteer.[interfacename])
    // 2) in GadgeteerHardware.xml, uncomment the lines under <Assemblies> so that end user apps using this module also add a reference.

    /// <summary>
    /// A module that uses shift registers to give the user 32 outputs, for Microsoft .NET Gadgeteer.
    /// </summary>
    /// <example>
    /// <para>The following example uses a <see cref="MaxO"/> object to write to a few of the 32 available outputs.
    /// At the end of the example, each alternating pin will be high, with the others low.
    /// </para>
    /// <code>
    /// using System;
    /// using System.Collections;
    /// using System.Threading;
    /// using Microsoft.SPOT;
    /// using Microsoft.SPOT.Presentation;
    /// using Microsoft.SPOT.Presentation.Controls;
    /// using Microsoft.SPOT.Presentation.Media;
    /// using Microsoft.SPOT.Touch;
    ///
    /// using Gadgeteer.Networking;
    /// using GT = Gadgeteer;
    /// using GTM = Gadgeteer.Modules;
    /// using Gadgeteer.Modules.GHIElectronics;
    ///
    /// namespace TestApp
    /// {
    ///     public partial class Program
    ///     {
    ///         void ProgramStarted()
    ///         {
    ///             // We will be using one module for this example.
    ///             maxO.NumBoards = 1;
    ///         
    ///             // The reason value 0xAA is used is because it is 1010 1010 in binary, which we will see in a moment on the module.
    ///             // Using this value will alternate the pins high and low.
    ///             byte[] arr = new byte[] { 0xAA, 0xAA, 0xAA, 0xAA };
    ///
    ///             // Next, we will write the data to the module.
    ///             maxO.WriteArray(arr);
    ///         }
    ///     }
    /// }
    /// </code>
    /// </example>
    public class MaxO : GTM.Module
    {
        private static GTI.Spi spi;
        private static GTI.SpiConfiguration config;

        private static GTI.DigitalOutput Enable;
        private static GTI.DigitalOutput CLR;

        private static byte[] data;
        private static bool reSized = false;

        private static int numBoards;

        /// <summary>
        /// The number of this type of module attached together
        /// </summary>
        public int NumBoards
        {
            get
            {
                return MaxO.numBoards;
            }
            set
            {
                if (!reSized)
                {
                    MaxO.numBoards = value;
                    data = new byte[numBoards * 4];
                    reSized = true;
                }
                else
                {
                    throw new Exception("Number of boards attached my not be changed once it has been set!");
                }
            }
        }

        /// <summary>
        /// Returns the size of the array to be filled out.
        /// </summary>
        /// <returns></returns>
        public int GetArraySize()
        {
            return data.Length;
        }

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public MaxO(int socketNumber)
        {
            // This finds the Socket instance from the user-specified socket number.  
            // This will generate user-friendly error messages if the socket is invalid.
            // If there is more than one socket on this module, then instead of "null" for the last parameter, 
            // put text that identifies the socket to the user (e.g. "S" if there is a socket type S)
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);
            socket.EnsureTypeIsSupported('S', this);

            config = new GTI.SpiConfiguration(false, 0, 0, false, true, 1000);
            spi = GTI.SpiFactory.Create(socket, config, GTI.SpiSharing.Shared, socket, Socket.Pin.Five, this);

            Enable = GTI.DigitalOutputFactory.Create(socket, Socket.Pin.Three, false, this);
            CLR = GTI.DigitalOutputFactory.Create(socket, Socket.Pin.Four, true, this);

            numBoards = 0;
        }

        /// <summary>
        /// Clears all registers.
        /// </summary>
        public void Clear()
        {
            if (!reSized)
            {
                ErrorPrint("The array has not been sixed yet. Please indicate how many modules are chained before continuing");
            }

            Enable.Write(true);
            CLR.Write(false);

            System.Threading.Thread.Sleep(10);
            byte[] clear = new byte[1] { 0 };
            spi.Write(clear);

            CLR.Write(true);
            Enable.Write(false);

            for (int i = 0; i < data.Length; i++)
                data[i] = 0x0;
        }

        /// <summary>
        /// Writes the full array of data to the registers.
        /// </summary>
        /// <param name="arr"></param>
        public void WriteArray(byte[] arr)
        {
            if (!reSized)
            {
                throw new Exception("No boards have been initialized! Please indicate how many modules are chained before writing");
            }

            if (arr.Length != data.Length)
            {
                throw new Exception("Passed in array not the same size as the number of registers!");
            }

            Enable.Write(true);
            {
                byte[] reversedArr = new byte[arr.Length];
                Array.Copy(arr, reversedArr, arr.Length);

                for (int i = 0; i < reversedArr.Length; i++)
                {
                    reversedArr[i] = arr[data.Length - i - 1];//(byte)(~reversedArr[i]);
                }

                spi.Write(reversedArr);
                Array.Copy(arr, data, arr.Length);
            }
            Enable.Write(false);
        }

        /// <summary>
        /// Writes only to the specified pin or the specified board.
        /// </summary>
        /// <param name="_board">The number of the board to write to.</param>
        /// <param name="_pin">The number of the pin to write.</param>
        /// <param name="_value">The value to write to the pin.</param>
        public void WritePin(int _board, int _pin, bool _value)
        {
            if (!reSized)
            {
                throw new Exception("No boards have been initialized! Please indicate how many modules are chained before writing");
            }

            // check to see if the pin is inside our range
            int length = ((_board) * 4);// +_pin;
            int position = ((_board - 1) * 4) + _pin;

            if (length > data.Length)
                throw new Exception("Invalid pin position. Pin out of range");

            // make a "dummy" to turn our pin on or off
            byte[] dummy = new byte[data.Length];

            Array.Copy(data, dummy, data.Length);

            // find exact bit position
            int blockPos = /*dummy.Length - */(((_board - 1) * 4) + (_pin / 8));
            //blockPos--;
            if (_value)
            {
                dummy[blockPos] = (byte)(data[blockPos] | (1 << ((_pin % 8) /*- 1*/)));
                WriteArray(dummy);
            }
            else
            {
                dummy[blockPos] = (byte)(data[blockPos] & ~(1 << ((_pin % 8) /*- 1*/)));
                WriteArray(dummy);
            }
        }

        /// <summary>
        /// Returns the data that is currently in the registers.
        /// </summary>
        /// <returns></returns>
        public byte[] Read()
        {
            if (!reSized)
            {
                throw new Exception("No boards have been initialized! Please indicate how many modules are chained before writing");
            }

            return data;
        }

        /// <summary>
        /// Enables output to the pins.
        /// </summary>
        public void EnableOutputs()
        {
            if (!reSized)
            {
                throw new Exception("No boards have been initialized! Please indicate how many modules are chained before writing");
            }

            Enable.Write(false);
        }

        /// <summary>
        /// Disables the output to the pins.
        /// </summary>
        public void DisableOutputs()
        {
            if (!reSized)
            {
                throw new Exception("No boards have been initialized! Please indicate how many modules are chained before writing");
            }

            Enable.Write(true);
        }
    }
}